/**
 * 函数组件&类组件对比
 * ----函数组件==》静态组件
 *     组件第一次渲染完毕之后，无法基于内部的某些操作实现组件的更新【无法实现自更新】， 但是，当调用它的父组件更新的时候，相关的子组件一定会更新，【可能传递最新的属性值进来】  
 *     函数组件具备：属性 [其他状态等内容几乎没有]
 *     函数组件优势：比类组件的处理机制简单，所以函数组件渲染速度更快
 * 
 * ----类组件==》动态组件
 *     组件在第一次渲染完毕之后，除了父组件的更新会触发子组件的更新以外，我们也可以通过this.setState修改状态以及this.forceUpdate强制更新来实现子组件的自更新
 *     类组件具备：属性 状态 周期函数 ref..... [几乎组件应该有的东西，类组件都具备]
 *     类组件优势：功能强大，能够实现自更新
 * 
 * 未来推荐=====》Hooks组件，具备了函数组件和类组件的各自优势，在函数组件的基础上，基于hooks函数，让函数组件也可以拥有状态 周期函数等，让函数组件也可以实现自更新【动态化】
 */

/**
 * 创建类组件
 * 创建一个构造函数（类）要求必须继承React.Component/PureComponent
 * 我们习惯于使用ES6中的class创建类
 * 必须给当前类设置一个render的方法（放在其原型上），在render方法中返回我们需要渲染的视图
 * 
 * 从调用类组件开始【new ClassVote({....})】,类组件内部发生了什么
 *    1：初始化属性 && 规则校验
 *      规则校验
 *      // 属性规则校验
        // 默认值 
        static defaultProps = {
          supNum: 0,
        };
        // 校验规则
        static propTypes = {
          title: PropTypes.string.isRequired,
          supNum: PropTypes.number,
        };
 *       ------
 *      规则校验完毕后往实例上挂载属性：
 *        方案一
 *        constructor(props) {
            super(props);  // 将传递进来的属性挂载到this实例上
            console.log('props======', this.props);  // 获取到传递的属性
          };
          方案二
        即便我们自己不在constructor中处理[或者constructor都没写]，在constructor处理完毕后，React内部也会把传递的props挂载到实例上，所以在其他函数中，只要保证this是实例，就可以通过this.props获取传递的属性
        同样this.props获取到的属性对象也是被冻结的【只读的】

 *    2：初始化状态[state]
        状态：后期修改状态，可以触发视图更新
        需要手动初始化，如果我们没有做相关操作，会默认往实例上挂载一个state,默认值为null; this.state = null;
          1：手动处理：
          state = {
            supNum: 10,
            oppNum: 5,
          };
        ------
          2：修改状态，控制视图更新
             想让视图更新需要基于React.Component.prototype上提供的的setState方法进行操作
             @1 this.setState(partialState); partialState: 部分状态
             @2 this.state.oppNum++; this.forceUpdate();  // 强制更新
      
  *     3：触发componentWillMount生命周期函数 组件第一次渲染之前
           钩子函数：在程序运行到某个阶段，我们基于提供的一个处理函数，然开发者在在这个阶段执行自定义操作

  *     4：触发render周期函数 渲染
  
  *     5：触发componentDidMount生命周期函数 组件第一次渲染完毕
           页面中已经创建了真实DOM元素，可以获取到DOM元素了 
  
  * 组件更新的逻辑（当修改了相关状态，组件会更新）
      第一种： 组件内部状态修改
          1：触发shouldComponentUpdate生命周期函数 是否允许更新【此生命周期函数需要返回true/false】
            shouldComponentUpdate(nextProps, nextState) {
              // return true; // 允许更新，会继续执行下一个操作
              // return false; // 不允许更新，接下来啥都不处理 
            }
            nextState===>存储要修改的最新状态
            this.state==>存储的是修改前的状态【此时状态还没有改变】

          2：触发componentWillUpdate生命周期函数，此时状态未改变

          3：修改状态值/属性值 【将this.state.??? 改为最新的值】

          4：触发render周期函数 组件更新
            按照最新的状态/属性，将返回的jsx编译为全新的virtualDom
            和第一次渲染出来的virtualDom进行对比，这就是我们所说的[DOM-DIFF]
            仅将差异部分进行渲染【渲染为真实Dom】

          5：触发componentDidUpdate生命周期函数（组件更新完毕）
      *** 特殊说明：如果通过forceUpdate强制更新，会跳过shouldComponentUpdate生命周期函数的校验，直接从componentWillUpdate进行更新 【也就是: 视图一定会触发更新】
      
      第二种：父组件更新触发子组件更新
          1：触发getDerivedStateFromProps生命周期函数 接受最新属性之前
             getDerivedStateFromProps(nextProps, prevState) {
               nextProps => 存储传递进来的最新属性
                this.props => 存储之前的属性【属性暂未修改】
             }
          2：shouldComponentUpdate
          3；componentWillUpdate
          4：render
          5：componentDidUpdate
          深度优先原则： 父组件在操作中，遇到子组件，一定是把子组件处理完之后，父组件才继续处理子组件
            @1 -- 父组件第一次渲染：父willMount => 父render[ 子willMount => 子render => 子didMount ] => 父didMount
            @2 -- 父组件更新： 父shouldUpdate => 父willUpdate => 父render[ 子getDerivedStateFromProps => 子shouldUpdate => 子willUpdate => 子render => 子didUpdate ] => 父didUpdate
            @3 -- 父组件销毁：父willUnmount => 处理中[ 子willUnmount => 子销毁 ] 父销毁
    
  * 组件销毁的逻辑
          1：触发componentWillUnmount生命周期函数 组件销毁之前
          2：组件销毁
  */

import React, { Component } from "react";
import PropTypes from 'prop-types';
class ClassVote extends React.Component {
  /* 属性规则校验 */
  // 默认值
  static defaultProps = {
    total: 0,
  };
  // 校验规则
  static propTypes = {
    title: PropTypes.string.isRequired,
    total: PropTypes.number,
  };

  /* 真实项目中写constructor的几率比较小，【需要通过属性值计 算出一个状态值并将状态赋值时 可能会在constructor中处理】 */
  // constructor(props) {
  //   // 可写可不写，需要接收传递进来的实参信息，才需要写constructor
  //   // console.log('lifecyce------constructor');
  //   // console.log('props------', this.props);
  //   super(props);
  //   console.log('props======', this.props);
  //   this.state = {
  //     children: React.Children.toArray(props.children),
  //   };
  // };

  /* 初始化状态 */
  state = {
    supNum: 20,
    oppNum: 5,
  };

  // 更新supNum
  updateSupNum = () => {
    this.setState(prevState => ({
    // setState函数通常是异步的（有些情况下是同步的），所以最好假设setState是异步的，并在需要在更新状态之后执行某些操作的时候使用setState的回调函数
    supNum: prevState.supNum + 1}), () => {
      // console.log("this.state.supNum", this.state.supNum);
    });
  };
  // 更新oppNum
  updateOppNum = () => {
    this.setState({
      oppNum: this.state.oppNum + 1
    });
    // this.state.oppNum++;
    // this.forceUpdate(); 
  };

  UNSAFE_componentWillMount() {
    console.log("lifecycle---UNSAFE_componentWillMount");
  };
  render() {
    console.log("lifecycle---render");
    // console.log(this);
    // console.log('lifecycle------render');
    // console.log("=======isFrozen", Object.isFrozen(this.props));
    let { title, total } = this.props;
    let { supNum, oppNum } = this.state;
    return (
      <div className="vote-box">
        <div className="header">
          <div className="title">{title}</div>
          <span>{supNum + oppNum}人</span>
        </div>
        <div className="main">
          <p>支持人数：{supNum}人</p>
          <p>反对人数：{oppNum}人</p>
        </div>
        <div className="footer">
          <button onClick={this.updateSupNum}>支持</button>
          <button onClick={this.updateOppNum}>反对</button>
        </div>
      </div>
    )
  };
  componentDidMount() {
    console.log("lifecycle---componentDidMount");
  };
  shouldComponentUpdate(nextProps, nextState) {
    console.log("lifecycle---shouldComponentUpdate----this.state&&nextState", this.state, nextState);
    return true;
  };
  componentDidUpdate(preProps,preState,snapshotValue){
    console.log('lifecycle---componentDidUpdate',preProps,preState,snapshotValue);
  };
  
  /**
   * React 17 版本已经标记 componentWillReceiveProps 方法为过时
   * 建议使用 getDerivedStateFromProps 或者在 componentDidUpdate 中进行相应的处理来替代,getDerivedStateFromProps 是  React 组件生命周期中的一个静态方法
   */
  // componentWillReceiveProps(nextProps) {
  //   console.log('lifecycle---componentWillReceiveProps',nextProps);
  // };
  static getDerivedStateFromProps(nextProps, prevState) {
    console.log('lifecycle---getDerivedStateFromProps', nextProps, prevState);
  };

};
export default ClassVote;

/**
 * 基于es5的方式实现寄生组合式继承
 */
function AAA () {
  Component.call(this);
  this.state = {
    a: 'a',
  }
};
Object.setPrototypeOf(AAA.prototype, Component.prototype);
AAA.prototype.sum = () => { console.log('sum') };


/**
 * ES6语法复习
 */     
class Parent {

  // 静态属性和方法：被存放在Fn构造函数中，与prototype同级
  // 私有属性和方法：被存放在实例化对象中
  // 公有属性和方法：被存放在Fn的prototype属性中

  // new 的时候 执行的构造函数，【可写可不写，需要接收传递进来的实参信息，才需要设置constructor】
  constructor(x, y) {
    // this => 创建的实例 
    this.total = x + y;
    this.subtract = x - y;
  };
  num = 200; // 等价于 this.num = 200 给实例添加私有属性
  getNum = () => { // 等价于 this.getNum = () => {}; 给实例添加私有方法
    // 箭头函数没有this，所用到的this是宿主环境中的
    // console.log("getNum======this", this); // this=> 当前创建的实例
    console.log("======this.total", this.total);
  };
  getSubtract() {
    // console.log("getSubtract======this", this);
    // 给Parent.prototype上添加公有方法, getSubtract函数是不可枚举的
    console.log("======this.subtract", this.subtract);
  };

  // 把构造函数当做一个对象，为其设置静态的私有属性和方法
  static average = 1000;
  static getSquare(x) {
    // console.log("======this.getSquare", x * x);
  };
}
Parent.prototype.feature = "great"; // 给构造函数原型上手动添加公有的属性
let p = new Parent(10, 20);
// console.log(p);
// p.getNum();
// p.getSubtract();
// console.log("=======ppppp", p);
class Son extends Parent {
}
let s = new Son(100, 200);

Parent.getSquare(2);
// p.getNum();
// p.getSubtract();
// s.getNum();
// s.getSubtract();
// console.log("======son", s);



/**
 * 基于extends实现继承
 * 1：首先基于call继承 React.Component.call(this) this => People类的实例p
 *    function Component(props, context, updater) {
 *        this.props = props;
 *        this.context = context;
 *        this.refs = emptyObject;
 *        this.updater = updater || ReactNoopUpdateQueue
 *    }
 *    给创建的peo实例设置四个私有属性 props/context/refs/updater
 * 2：再基于原型继承来处理 People.prototype.__proto__ ====> Component.prototype
 *    实例属性查找顺序 实例 ==》People.prototype ==》Component.prototype ==》 Object.prototype
 *    实例除了具备People原型上提供的公共方法以外，还具备Component.prototype原型上提供的 
 *    属性：isReactComponent 和方法：forceUpdate/setState
 * 3：只要自己设置了constructor，则内部第一句话必须执行super()
 */
class People extends Component {
  constructor(x, y) {
    // this => 实例person
    super(10, 20, 30); // 等价于 Component.call(this);
  }
}
let person = new People();
// console.log(person);


